Skip to content

pmdadb2: fix Db2 v12 MON_* column and view compatibility#2625

Merged
natoscott merged 3 commits into
performancecopilot:mainfrom
kurik:db2
Jun 24, 2026
Merged

pmdadb2: fix Db2 v12 MON_* column and view compatibility#2625
natoscott merged 3 commits into
performancecopilot:mainfrom
kurik:db2

Conversation

@kurik

@kurik kurik commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Update SQL for Db2 12 (TBSP_NAME, ACT_COMPLETED_TOTAL, SYSIBMADM.ENV_INST_INFO, transaction log byte fields), report zero for unavailable per-table deadlocks, and only drop connections on communication failures.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of lost/unusable Db2 connections to avoid unnecessary resets.
    • Enhanced user table discovery and refresh so per-table monitoring more reliably includes all relevant tables.
  • Changes

    • Refined Db2 metric semantics, including “statements completed” remapping, tablespace naming/source adjustments, and updated instance version sourcing.
    • Updated per-table deadlock behavior where applicable.
    • Adjusted transaction log “used”/“available” calculations and corresponding source descriptions.

Walkthrough

Updates the Db2 PMDA in pmdadb2.python to align SQL cluster queries with correct Db2 view column names (TBSP_NAME, ACT_COMPLETED_TOTAL, SYSIBMADM.ENV_INST_INFO, log KB derivation), introduces a row_val callback for per-row metric customization, adds connection-loss classification, implements user table discovery, and updates metric help text throughout.

Changes

Db2 PMDA metric and runtime fixes

Layer / File(s) Summary
SQL cluster column name corrections
src/pmdas/db2/pmdadb2.python
Tablespace indom and cluster SQL switch to TBSP_NAME; database cluster item 18 uses ACT_COMPLETED_TOTAL; instance cluster switches to SYSIBMADM.ENV_INST_INFO with SERVICE_LEVEL and BLD_LEVEL columns; transaction log SQL derives KB values via TOTAL_LOG_USED / 1024 and TOTAL_LOG_AVAILABLE / 1024; MON_GET_TABLE select-list removes deadlock field and shifts metric positions.
Table deadlock row_val callback and refresh signature refactor
src/pmdas/db2/pmdadb2.python
MON_GET_TABLE metric registration is realigned; db2_refresh_instanced() gains an optional row_val=None callback parameter that replaces row[item+1] indexing when provided; cluster metadata is used to compute indom internally; per-row metric values are computed via the callback when supplied.
User table discovery and connection classification
src/pmdas/db2/pmdadb2.python
self.known_tables tracks user tables per database; new db2_quote_ident() and db2_register_user_tables() probe SYSCAT.TABLES and execute per-table COUNT(*) queries to enable MON_GET_TABLE visibility; new _db2_connection_lost() classifies exceptions by SQLSTATE and handle liveness; db2_exec() selectively clears self.conns[dbname] only when the connection is deemed unusable; db2_cursor() and refresh dispatchers re-initialize and re-register user tables on reconnection.
Metric help text and Source: string updates
src/pmdas/db2/pmdadb2.python
All Source: strings updated for db2.database.stmts_completed, db2.log.used_kb, db2.log.available_kb, and db2.instance.* metrics to reference corrected column sources; cluster 9 comment updated to reference SYSIBMADM.ENV_INST_INFO; db2.table.deadlocks help text documents that per-table deadlock counts are always zero on affected Db2 versions and directs users to database-wide deadlock metrics.

Possibly related PRs

  • performancecopilot/pcp#2621: Introduced the DB2PMDA class and its core methods (db2_refresh_instanced, db2_exec, cluster SQL definitions, and initialization) that this PR directly extends with user table discovery (db2_register_user_tables, db2_quote_ident), connection classification (_db2_connection_lost), the row_val callback parameter, and comprehensive metric documentation updates.

Poem

🐇 A rabbit checks the columns with care,
TBSP_NAME and ACT_COMPLETED found there.
User tables probed, connections assessed,
BLD_LEVEL fields now freshly redressed.
Each Source: string points true and bright—
The metrics hop through views, set right! 🐾

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main objective of the PR, which is updating SQL queries and compatibility for Db2 v12 MON_* columns and views.
Description check ✅ Passed The description clearly relates to the changeset by summarizing key updates (TBSP_NAME, ACT_COMPLETED_TOTAL, SYSIBMADM.ENV_INST_INFO, transaction log fields, connection handling, and deadlock reporting).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kurik

kurik commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

This is a followup of #2621
Tested with DB2-11.5.9.0 and DB2-12.1.4.0 Community editions as back-end.

@natoscott I can not make db2.table.* metrics working. My AI agent is complaining, that those metrics were per-table in older DB2 versions, while for DB2 v11 and v12 these metrics are per-database and suggesting to remove these from the PMDA. Perhaps we might need some expert here, as discussed elsewhere.

@natoscott

Copy link
Copy Markdown
Member

This is a followup of #2621 Tested with DB2-11.5.9.0 and DB2-12.1.4.0 Community editions as back-end.

@natoscott I can not make db2.table.* metrics working. My AI agent is complaining, that those metrics were per-table in older DB2 versions, while for DB2 v11 and v12 these metrics are per-database and suggesting to remove these from the PMDA. Perhaps we might need some expert here, as discussed elsewhere.

@kurik Awesome - thanks for the investigation and fixes — the TABLESPACE_NAME -> TBSP_NAME, ACT_COMPLETED_TOTAL, SYSIBMADM.ENV_INST_INFO, and log byte-field changes all look good to me.

One concern about the DEADLOCKS handling: keeping the metric returning a hardcoded zero is misleading — this way if monitoring db2.table.deadlocks we cannot distinguish "no deadlocks occurred" from "this data is not available at the table level". Better to remove it entirely.

The same applies to the other lock-related columns in the MON_GET_TABLE query: LOCK_WAITS, LOCK_WAIT_TIME, and LOCK_ESCALS. If DEADLOCKS is not available per-table in Db2 v11/v12, these may have the same limitation. Could you verify whether these three columns return meaningful per-table values on your 12.1 instance, or whether they too reflect only database-wide aggregates?

If they are also unavailable per-table, we should remove all four lock-related metrics (db2.table.deadlocks, db2.table.lock_waits, db2.table.lock_wait_time, db2.table.lock_escalations) from the cluster rather than stub them. The row DML counters, overflow accesses, and table scan counts would remain as the meaningful per-table metrics.

kurik added 3 commits June 23, 2026 19:05
Update SQL for Db2 12 (TBSP_NAME, ACT_COMPLETED_TOTAL,
SYSIBMADM.ENV_INST_INFO, transaction log byte fields), report zero for
unavailable per-table deadlocks, and only drop connections on
communication failures.
To make pylint happy, the number of parameters passed to
db2_refresh_instanced() has been reworked.
MON_GET_TABLE only lists tables after the monitoring connection has
accessed them, so db2.table.* was always empty despite application
activity. Probe user tables from SYSCAT on connect, reconnect, and
before table refreshes so per-table counters are populated. Also remove
the stub db2.table.deadlocks metric, which is not available in
MON_GET_TABLE and was misleading when hardcoded to zero.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pmdas/db2/pmdadb2.python`:
- Around line 1319-1320: Create a new helper method db2_exec_probe that executes
lightweight SQL probes using SELECT 1 with FETCH FIRST 1 ROW ONLY WITH UR
instead of COUNT(*), which avoids unnecessary full-table scans. The helper
should use ibm_db.exec_immediate and ibm_db.fetch_tuple to fetch at most one
row, handle exceptions with appropriate error logging, and detect connection
loss. Then replace all COUNT(*) probe calls in db2_register_user_tables and the
other affected locations (mentioned as also applying to lines 1373-1376 and
1402-1404) with calls to this new db2_exec_probe helper method.
- Around line 1248-1256: The metric definition for PM_ID(9, 2) named
'db2.instance.platform' is currently returning BLD_LEVEL data which represents a
build identifier, not platform/OS information, creating a semantic mismatch
between the metric name and its actual content. Either locate an alternative Db2
data source that provides true platform metadata and update the metric to use
that instead of BLD_LEVEL, or rename the metric from 'db2.instance.platform' to
'db2.instance.build_level' and update all references to this metric name in
downstream files including the manual page (pmdadb2.1) and the pmlogconf.summary
configuration file to reflect the new name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 896620b2-151d-468b-a9f8-9fe74430bc06

📥 Commits

Reviewing files that changed from the base of the PR and between e71d0f6 and d494189.

📒 Files selected for processing (1)
  • src/pmdas/db2/pmdadb2.python

Comment on lines 1248 to +1256
self.PM_ID(9, 2) : ['db2.instance.platform',
pmdaMetric(self.PM_ID(9, 2), c_api.PM_TYPE_STRING,
c_api.PM_INDOM_NULL, c_api.PM_SEM_DISCRETE, noUnits),
{},
'Db2 instance operating platform',
'Operating system and CPU architecture on which the Db2\n'
'instance is running, for example "Linux/X8664".\n'
'Source: ENV_GET_INSTANCE_INFO.PLATFORM'],
'Db2 instance build level identifier',
'Db2 build level string from the instance environment,\n'
'which often encodes CPU architecture (e.g. AMD64). PLATFORM\n'
'is not available in SYSIBMADM.ENV_INST_INFO on Db2 v12.\n'
'Source: SYSIBMADM.ENV_INST_INFO.BLD_LEVEL'],

@coderabbitai coderabbitai Bot Jun 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, explore repository structure
find . -name "pmdadb2.python" -o -name "pmdadb2.1" -o -name "pmlogconf.summary" | head -20

Repository: performancecopilot/pcp

Length of output: 902


🏁 Script executed:

# Also check if this is a git repository to see what changed
git ls-files | grep -E "(pmdadb2|pmlogconf)" | head -20

Repository: performancecopilot/pcp

Length of output: 831


🏁 Script executed:

# Get file size to determine reading strategy
wc -l src/pmdas/db2/pmdadb2.python 2>/dev/null || echo "File not found, trying alternate path"

Repository: performancecopilot/pcp

Length of output: 194


🏁 Script executed:

# Read the specific lines mentioned in the review
sed -n '1240,1270p' src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 1667


🏁 Script executed:

# Check git history/diff for this file to understand what changed
git diff HEAD src/pmdas/db2/pmdadb2.python | head -100

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Also check if there's git log info for recent changes
git log --oneline -10 -- src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 237


🏁 Script executed:

# Check the downstream reference in pmdadb2.1
grep -A 5 -B 5 "db2.instance.platform" src/pmdas/db2/pmdadb2.1

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Check the downstream reference in pmlogconf.summary
grep -A 5 -B 5 "db2.instance.platform" src/pmdas/db2/pmlogconf.summary

Repository: performancecopilot/pcp

Length of output: 342


🏁 Script executed:

# Check if there are other metrics in cluster 9 to understand the context better
sed -n '1230,1260p' src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 1909


🏁 Script executed:

# Check the end of the file for the trailing newline requirement
tail -c 50 src/pmdas/db2/pmdadb2.python | od -c

Repository: performancecopilot/pcp

Length of output: 200


🏁 Script executed:

# Search more broadly in pmdadb2.1 for platform-related documentation
cat src/pmdas/db2/pmdadb2.1 | grep -i platform

Repository: performancecopilot/pcp

Length of output: 223


🏁 Script executed:

# Check the full context around cluster 9 in pmdadb2.1
sed -n '1,100p' src/pmdas/db2/pmdadb2.1 | grep -A 20 -B 5 "instance"

Repository: performancecopilot/pcp

Length of output: 2389


🏁 Script executed:

# Check for trailing newline using a different approach
tail -c 10 src/pmdas/db2/pmdadb2.python | xxd

Repository: performancecopilot/pcp

Length of output: 201


🏁 Script executed:

# Look at the help text more carefully - check what data is actually fetched
rg "BLD_LEVEL" src/pmdas/db2/pmdadb2.python -A 2 -B 2

Repository: performancecopilot/pcp

Length of output: 611


🏁 Script executed:

# Check trailing newline requirement more carefully
tail -c 1 src/pmdas/db2/pmdadb2.python | wc -l

Repository: performancecopilot/pcp

Length of output: 162


🏁 Script executed:

# Also search for what platform data would represent vs build level
web_search "Db2 PLATFORM metadata ENV_INST_INFO vs BLD_LEVEL"

Repository: performancecopilot/pcp

Length of output: 208


🌐 Web query:

Db2 SYSIBMADM.ENV_INST_INFO columns platform metadata

💡 Result:

In Db2, SYSIBMADM.ENV_INST_INFO is an administrative view that returns information about the current DB2 instance [1]. However, the IBM documentation page we found does not include a column-by-column listing of SYSIBMADM.ENV_INST_INFO (at least not in the accessible “table of columns” form), so I can’t reliably enumerate every column name and its datatype/platform applicability from authoritative sources in this run. What we can ground from sources: - The view is schema SYSIBMADM and returns “current instance information” [1]. - Example usage: SELECT * FROM SYSIBMADM.ENV_INST_INFO [2]. - One specific column is INST_NAME (instance name) [3]. - Another specific column is INST_PTR_SIZE (indicates 32 vs 64-bit instance) [4]. If you want, tell me your Db2 family/product and version (LUW/Db2 Warehouse/z/OS/Db2 for i) and I can re-run a targeted search to extract the full “Information returned” column list (names + datatypes) for your exact product/version.

Citations:


🏁 Script executed:

# Check if there's any reference to PLATFORM in the database code
rg "PLATFORM" src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 241


🏁 Script executed:

# Let me also check the actual SQL query being run and what columns are available
sed -n '1200,1230p' src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 2105


🏁 Script executed:

# Check if there's a git diff to see the actual change (old vs new)
git show HEAD:src/pmdas/db2/pmdadb2.python 2>/dev/null | sed -n '1240,1260p' | head -40

Repository: performancecopilot/pcp

Length of output: 1332


🏁 Script executed:

# Check if INST_PTR_SIZE or other columns might indicate platform
rg "INST_PTR_SIZE|INST_CODEPAGE" src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Look for any comments about the PLATFORM column availability
rg -B 5 -A 5 "PLATFORM.*not available" src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Check what the previous version had - look at git history
git log --all --oneline src/pmdas/db2/pmdadb2.python | head -5

Repository: performancecopilot/pcp

Length of output: 514


🏁 Script executed:

# Get a specific commit to see what was changed
git show d4941899e:src/pmdas/db2/pmdadb2.python 2>/dev/null | sed -n '1240,1260p'

Repository: performancecopilot/pcp

Length of output: 1332


🏁 Script executed:

# Search for any PLATFORM column references in commit history
git log -p --all -S "PLATFORM" -- src/pmdas/db2/pmdadb2.python 2>/dev/null | head -50

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Check the commit that added discrete instance metrics
git show 117b55d22:src/pmdas/db2/pmdadb2.python 2>/dev/null | sed -n '1240,1260p'

Repository: performancecopilot/pcp

Length of output: 1464


🏁 Script executed:

# Also check what commit 9b1687674 changed (fix SQL for Db2 12.1)
git show 9b1687674:src/pmdas/db2/pmdadb2.python 2>/dev/null | sed -n '1240,1260p'

Repository: performancecopilot/pcp

Length of output: 1380


🏁 Script executed:

# Check the full diff between commits to understand the evolution
git diff 117b55d22^..117b55d22 -- src/pmdas/db2/pmdadb2.python 2>/dev/null | grep -A 30 "PM_ID(9, 2)" | head -50

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Get the full metric definition from commit 117b55d22 for PM_ID(9,2)
git show 117b55d22:src/pmdas/db2/pmdadb2.python 2>/dev/null | sed -n '1230,1270p'

Repository: performancecopilot/pcp

Length of output: 2639


🏁 Script executed:

# Check the diff between 117b55d22 and 9b1687674 to see what changed for this metric
git diff 117b55d22..9b1687674 -- src/pmdas/db2/pmdadb2.python 2>/dev/null | grep -A 25 -B 5 "PM_ID(9, 2)"

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Get more specific diff context showing the exact change
git show 9b1687674 -- src/pmdas/db2/pmdadb2.python 2>/dev/null | grep -A 50 "instance.platform"

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Check what the SQL query change was between versions
git diff 117b55d22..9b1687674 -- src/pmdas/db2/pmdadb2.python 2>/dev/null | grep -B 5 -A 10 "ENV_INST_INFO\|cluster_inst"

Repository: performancecopilot/pcp

Length of output: 160


🏁 Script executed:

# Check the current state and the SQL query for cluster 9
grep -A 10 "cluster_inst = {" src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 617


🏁 Script executed:

# Check if there's a PLATFORM column in the help text in current version
sed -n '1248,1256p' src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 726


🏁 Script executed:

# Look for any reference to ENV_GET_INSTANCE_INFO vs SYSIBMADM.ENV_INST_INFO
rg "ENV_GET_INSTANCE_INFO|ENV_INST_INFO" src/pmdas/db2/pmdadb2.python

Repository: performancecopilot/pcp

Length of output: 634


Do not return build level under db2.instance.platform.

The metric name still promises platform/OS information, but the value is now BLD_LEVEL, a build identifier that "often encodes" CPU architecture but is semantically distinct from platform metadata. This silently changes the public metric contract. The downstream documentation (pmdadb2.1) describes this metric as "operating platform," and pmlogconf.summary still includes it under that name. Since SYSIBMADM.ENV_INST_INFO lacks a true PLATFORM column in Db2 v12, either source platform metadata from an alternative location, or rename the metric to db2.instance.build_level and update all downstream references (docs and config files) to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pmdas/db2/pmdadb2.python` around lines 1248 - 1256, The metric definition
for PM_ID(9, 2) named 'db2.instance.platform' is currently returning BLD_LEVEL
data which represents a build identifier, not platform/OS information, creating
a semantic mismatch between the metric name and its actual content. Either
locate an alternative Db2 data source that provides true platform metadata and
update the metric to use that instead of BLD_LEVEL, or rename the metric from
'db2.instance.platform' to 'db2.instance.build_level' and update all references
to this metric name in downstream files including the manual page (pmdadb2.1)
and the pmlogconf.summary configuration file to reflect the new name.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid, I will fix this before merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@natoscott, thanks for confirming. When you address this, the recommended path is to rename db2.instance.platformdb2.instance.build_level (updating the metric name, one-line, and long-help text in pmdadb2.python) and then sync the downstream references in src/pmdas/db2/pmdadb2.1 and src/pmdas/db2/pmlogconf.summary to match the new name. That keeps the public metric contract accurate and avoids surprising anyone who scripts against the old db2.instance.platform name expecting OS/architecture strings.

Comment on lines +1319 to +1320
self.known_tables[dbname] = set()
self.db2_register_user_tables(dbname)

@coderabbitai coderabbitai Bot Jun 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, check file exists and get basic info
file "src/pmdas/db2/pmdadb2.python"
wc -l "src/pmdas/db2/pmdadb2.python"

Repository: performancecopilot/pcp

Length of output: 232


🏁 Script executed:

# Read the key sections mentioned in the review
echo "=== Lines 1315-1325 (initial connection area) ==="
sed -n '1315,1325p' "src/pmdas/db2/pmdadb2.python"

echo -e "\n=== Lines 1370-1380 (probe query area) ==="
sed -n '1370,1380p' "src/pmdas/db2/pmdadb2.python"

echo -e "\n=== Lines 1398-1410 (reconnect area) ==="
sed -n '1398,1410p' "src/pmdas/db2/pmdadb2.python"

Repository: performancecopilot/pcp

Length of output: 1755


🏁 Script executed:

# Find the db2_register_user_tables method definition
echo "=== Looking for db2_register_user_tables definition ==="
rg -n "def db2_register_user_tables" "src/pmdas/db2/pmdadb2.python" -A 30

Repository: performancecopilot/pcp

Length of output: 1821


🏁 Script executed:

# Check if file ends with newline
echo "=== Checking file ending ==="
tail -c 20 "src/pmdas/db2/pmdadb2.python" | od -c

Repository: performancecopilot/pcp

Length of output: 229


🏁 Script executed:

# Check trailing newline using tail and hexdump
tail -c 1 "src/pmdas/db2/pmdadb2.python" | hexdump -C

Repository: performancecopilot/pcp

Length of output: 205


🌐 Web query:

Db2 COUNT(*) performance FETCH FIRST 1 ROW ONLY full table scan optimization

💡 Result:

In Db2, combining COUNT() with FETCH FIRST 1 ROW ONLY does not optimize a full table scan [1][2]. The COUNT() function is an aggregate operation that requires processing the specified rows to calculate the result [1][2]. Even when restricted by FETCH FIRST 1 ROW ONLY, the engine must still evaluate the rows to determine the count [2]. If your goal is to determine whether any rows exist in a table, using COUNT() is inefficient because it counts all matching rows (or up to the limit) rather than stopping at the first match [1][2]. Recommended alternatives for existence checks include: 1. EXISTS Predicate: Using an EXISTS clause in a subquery or conditional statement is the standard best practice [2]. The database engine stops processing as soon as it identifies a single qualifying row, significantly reducing I/O and CPU overhead [2]. Example: IF EXISTS (SELECT 1 FROM table WHERE condition) THEN... END IF; 2. Selecting a Constant: For simple existence checks, you can select a constant and fetch a single row: SELECT 1 FROM table WHERE condition FETCH FIRST 1 ROW ONLY; This approach performs better than COUNT() because the database does not need to aggregate or scan more than the first matching entry [1][2]. Performance Considerations: - FETCH FIRST 1 ROW ONLY (or OPTIMIZE FOR 1 ROW) influences the optimizer to prioritize fast access to the first row, but it does not transform an aggregate COUNT(*) into an existence check [3][4]. - Always ensure appropriate indexes exist on columns used in your WHERE clause to avoid full table scans regardless of the technique used [2]. - If you are testing existence, avoid counting rows entirely [1][2]. Use EXISTS or a limited fetch of a constant to allow the query to terminate early [2].

Citations:


🌐 Web query:

Db2 SQL WITH UR isolation level SELECT 1 query performance probe

💡 Result:

In Db2, the WITH UR (Uncommitted Read) isolation level provides a performance optimization by allowing an application to read data without acquiring most shared locks [1][2][3]. This significantly reduces lock contention and avoids "lock-wait" scenarios, making it faster than higher isolation levels like Cursor Stability (CS) [4][5]. However, the use of WITH UR comes with the trade-off of potential data inconsistency, as it allows for "dirty reads"—reading data that has been modified by other transactions but not yet committed [1][2][5]. Key considerations for a SELECT 1 query performance probe with WITH UR include: 1. Performance Characteristics: Using WITH UR for a simple SELECT 1 (or SELECT constant) query is highly performant because the database engine bypasses the overhead of acquiring and managing shared locks [2][4][3]. In a high-concurrency environment where other transactions are frequently updating data, this avoids the query stalling while waiting for those updates to complete [4]. 2. Appropriate Use Cases: WITH UR is best suited for scenarios where absolute data accuracy is not required, or where inconsistencies are acceptable [2][5]. Examples include: - Queries against rarely updated, read-only reference tables [2]. - Statistical analysis where minor inaccuracies from reading uncommitted data have negligible impact [2]. - Checking for existence when the "dirty" state is acceptable for the application logic [2]. 3. Limitations and Restrictions: - Read-only constraint: The WITH UR clause applies strictly to read-only operations (e.g., SELECT, SELECT INTO) [2][3]. If specified on a statement that Db2 determines is not read-only, it will default to Cursor Stability (CS) [6][3]. - Data Integrity: It should be avoided when transaction balance, exact accuracy, or strict data consistency is required [2]. 4. SELECT 1 Behavior: A SELECT 1 query is often used to verify the existence of a row [7]. When combined with WITH UR, it provides a lightweight existence check that is less likely to be blocked by concurrent writers, making it a common pattern in performance-sensitive applications where a "best-effort" existence check is sufficient [4][7]. For most applications, Cursor Stability (CS) remains the recommended default because it offers a balance of high concurrency and guaranteed data integrity, avoiding the risks associated with dirty reads [5].

Citations:


Replace COUNT(*) probe with lightweight SELECT 1 to avoid full-table scans during table discovery.

Db2's COUNT(*) is an aggregate function that processes rows even when combined with FETCH FIRST 1 ROW ONLY, resulting in unnecessary full-table scans. This issue is compounded because db2_register_user_tables() runs on startup and reconnect, inflating metrics like ROWS_READ and TABLE_SCANS that the PMDA reports. Use SELECT 1 ... FETCH FIRST 1 ROW ONLY WITH UR instead, which stops after accessing the first row and avoids lock contention without data integrity concerns for an existence probe.

Suggested direction
             if self.conns.get(dbname) is not None:
                 connected += 1
                 self.known_tables[dbname] = set()
-                self.db2_register_user_tables(dbname)
@@
-            probe = (
-                'SELECT COUNT(*) FROM %s.%s FETCH FIRST 1 ROW ONLY' %
-                (self.db2_quote_ident(schema), self.db2_quote_ident(table)))
-            if self.db2_exec(dbname, probe):
+            probe = (
+                'SELECT 1 FROM %s.%s FETCH FIRST 1 ROW ONLY WITH UR' %
+                (self.db2_quote_ident(schema), self.db2_quote_ident(table)))
+            if self.db2_exec_probe(dbname, probe):
                 self.known_tables[dbname].add(key)
@@
                 else:
                     self.known_tables[dbname] = set()
-                    self.db2_register_user_tables(dbname)

Add a lightweight helper that executes/fetches at most one row and returns True on successful execution, even for empty tables:

def db2_exec_probe(self, dbname, sql):
    conn = self.db2_cursor(dbname)
    try:
        stmt = ibm_db.exec_immediate(conn, sql)
        ibm_db.fetch_tuple(stmt)
        return True
    except Exception as error:
        self.error("db2_exec_probe", "%s: query '%s': %s" %
                   (dbname, sql[:60], str(error)))
        if self._db2_connection_lost(conn, error):
            self.conns[dbname] = None
        return False

Also applies to: 1373-1376, 1402-1404

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pmdas/db2/pmdadb2.python` around lines 1319 - 1320, Create a new helper
method db2_exec_probe that executes lightweight SQL probes using SELECT 1 with
FETCH FIRST 1 ROW ONLY WITH UR instead of COUNT(*), which avoids unnecessary
full-table scans. The helper should use ibm_db.exec_immediate and
ibm_db.fetch_tuple to fetch at most one row, handle exceptions with appropriate
error logging, and detect connection loss. Then replace all COUNT(*) probe calls
in db2_register_user_tables and the other affected locations (mentioned as also
applying to lines 1373-1376 and 1402-1404) with calls to this new db2_exec_probe
helper method.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid, I will fix this before merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@natoscott, sounds good — thanks for the confirmation. The key points to implement when you make the fix:

  1. Replace SELECT COUNT(*) FROM %s.%s FETCH FIRST 1 ROW ONLY with SELECT 1 FROM %s.%s FETCH FIRST 1 ROW ONLY WITH UR
  2. Add a dedicated db2_exec_probe helper that returns True even on empty tables (a successful exec_immediate with no rows fetched still means the table is accessible), distinct from db2_exec which returns a list of rows
  3. Use the same probe pattern in db2_cursor's reconnect path (db2_register_user_tables(dbname) call at Line 1404)

The WITH UR isolation level is especially appropriate here since the probe only needs to confirm table accessibility, not read consistent data.

@natoscott natoscott merged commit d494189 into performancecopilot:main Jun 24, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants